package com.arpa.wms.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.arpa.ntocc.common.common.util.UserUtil;
import com.arpa.ntocc.common.domain.entity.Shipment;
import com.arpa.ntocc.common.service.IShipmentService;
import com.arpa.wms.cache.GoodsCache;
import com.arpa.wms.cache.LocationBearTypeCache;
import com.arpa.wms.cache.LocationCache;
import com.arpa.wms.domain.entity.*;
import com.arpa.wms.domain.vo.ReceiveItemVO;
import com.arpa.wms.domain.vo.ReceiveItemWithRegisterVO;
import com.arpa.wms.mapper.LocationMapper;
import com.arpa.wms.service.IGoodsUnitConversionService;
import com.arpa.wms.service.IInventoryService;
import com.arpa.wms.service.IReceiveItemService;
import com.arpa.wms.service.WarehouseInternalStrategy;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
import static com.arpa.wms.domain.consts.CodeConst.*;

/**
 * 库内策略
 * 包含上架推荐策略，库存分配策略等
 *
 * @author leo
 */
@Service
public class WarehouseInternalStrategyImpl implements WarehouseInternalStrategy {

    @Resource
    private GoodsCache goodsCache;


    private final LocationBearTypeCache locationBearTypeCache;

    private final LocationCache locationCache;

    @Resource
    private LocationMapper locationMapper;

    private final IInventoryService inventoryService;

    private final IReceiveItemService receiveItemService;

    private final IShipmentService shipmentService;


    @Autowired
    private IGoodsUnitConversionService goodsUnitConversionService;

    public WarehouseInternalStrategyImpl(LocationBearTypeCache locationBearTypeCache,
                                         LocationCache locationCache,
                                         IInventoryService inventoryService,
                                         IReceiveItemService receiveItemService, IShipmentService shipmentService) {
        this.locationBearTypeCache = locationBearTypeCache;
        this.locationCache = locationCache;
        this.inventoryService = inventoryService;
        this.receiveItemService = receiveItemService;
        this.shipmentService = shipmentService;
    }

    /**
     * 托盘上架推荐库位
     * 按托盘将每个托盘的商品推荐到同一个库位上；
     * 根据库位承载类型判断是否可以推荐，可推荐库位：
     * a)库位承载类型可承载托盘数量=1且为空；
     * b)库位承载类型可承载托盘数量>1且已存放托盘数量<可承载托盘数量；
     * c)库位推荐限制：出入库锁：出库锁、不锁；
     * d)库位推荐顺序：上架动线号从小到大，如果没有维护上架动线号，按照库位编码从
     * 小到大；
     *
     * @return
     */
    @Override
    public Location recommendedLocationForPallet(String warehouseCode) {
        Location recommendedLocation = null;
        //获取所有库位承载类型
        List<Map<String, Object>> locationBearTypeListTest = locationBearTypeCache.getAllByGroupCode(UserUtil.getBranchCode());
        //获取托盘数量>1的承载类型
        List<LocationBearType> locationBearTypes = locationBearTypeListTest.stream()
                .filter(r -> MapUtil.getInt(r, "quantity") > 0)
                .map(l -> BeanUtil.mapToBean(l, LocationBearType.class, false))
                .collect(Collectors.toList());
        //承载类型Code
        String locationBearTypeCodes = locationBearTypes.stream().map(LocationBearType::getCode).collect(Collectors.joining(","));

        //获取仓库内所有库位
        List<Location> locationsCache = locationMapper.getAllEffectiveByWarehouseCode(warehouseCode);
        //库内所有可以存放托盘的库位
        //TODO 排除已推荐未上架的库位
        List<Location> locationList = locationsCache.stream()
                .filter(r -> StrUtil.equalsAny(r.getIoLock(), "1", "3") &&
                        StrUtil.isNotBlank(r.getBearType()) &&
                        StrUtil.containsAny(locationBearTypeCodes, r.getBearType()))
                .collect(Collectors.toList());

        if (IterUtil.isNotEmpty(locationList)) {
            //库位库存为空或托盘数小于库位承载数量,并按上架动线号从小到大排序
            List<Location> locations = locationList.stream().filter(r -> {
                Map<String, Object> bearType = locationBearTypeCache.get(r.getBearType());
                if (bearType == null ) {
                    return false;
                }
                Integer palletCount = inventoryService.countPallet(r.getCode());
                return (palletCount == null ? 0 : palletCount.intValue()) < ObjectUtil.defaultIfNull(MapUtil.getInt(bearType, "quantity"), 0);
            }).sorted(Comparator.comparing(Location::getShelvesNumber, Comparator.nullsLast(Comparator.naturalOrder()))).collect(Collectors.toList());
            //先库位动盘号排序，如果没有相应库位就按库位编码排序
            if (IterUtil.isNotEmpty(locations)) {
                recommendedLocation = locations.stream()
                        .sorted(Comparator.comparing(Location::getShelvesNumber, Comparator.nullsLast(Comparator.naturalOrder())))
                        .findFirst().orElse(locations.stream().sorted(Comparator.comparing(Location::getSerialNumber, Comparator.nullsLast(Comparator.naturalOrder()))).findFirst().orElse(null));
            }
        }

        return recommendedLocation;
    }


    /**
     * 商品混放上架库位推荐
     * 1、先推荐批号已存在的库位
     *
     * @param warehouseCode   仓库code
     * @param receiveRegister 收货登记记录
     * @return
     */
    @Override
    public String recommendedLocationForMixedGoods(String warehouseCode, ReceiveRegister receiveRegister) {

        //获取收货明细信息
        ReceiveItemVO receiveItem = receiveItemService.getOneByCode(receiveRegister.getReceiveItemCode());

        //生成批号
        String lot = generatorLot(receiveRegister, receiveItem.getShipmentCode(), receiveItem.getGoodCode());

        //查询该批号库存是否已经有
        List<Inventory> inventories = inventoryService.list(new QueryWrapper<Inventory>().lambda()
                .eq(Inventory::getLot, lot));

        if (IterUtil.isNotEmpty(inventories)) {
            //过滤商品状态和未锁定
            List<Location> locationList = inventories.stream()
                    .filter(f -> f.getIsLock().equals(0) && StrUtil.equalsIgnoreCase(f.getGoodsStatus(), receiveRegister.getGoodsStatus()))
                    .map(r -> BeanUtil.mapToBean(locationCache.get(r.getLocation()), Location.class, false))
                    .collect(Collectors.toList());
            //所有符合库位的可用存货数量
            List<Map<String, Object>> locationRemainingBearNum = new ArrayList<>();
            locationList.forEach(m -> {
                Map<String, Object> map = new HashMap<>();
                map.put("location", m);
                Map<String, Object> bearType = locationBearTypeCache.get(m.getBearType());
                double bearNum = MapUtil.getDouble(bearType, "num");
                //库位实际承载数量
                map.put("bearNum", bearNum);
                List<Inventory> inventorieTemps = inventoryService.list(new QueryWrapper<Inventory>().lambda().eq(Inventory::getLocation, m.getCode()).gt(Inventory::getQuantity, 0));
                //库存数量
                BigDecimal quantity = inventorieTemps.stream().map(Inventory::getQuantity).reduce(BigDecimal.ZERO, BigDecimal::add);
                //库位剩余承载数量
                BigDecimal remainingQuantity = BigDecimal.valueOf(bearNum).subtract(quantity);
                if (remainingQuantity.signum() == 1) {
                    map.put(REMAINING_QUANTITY, remainingQuantity);
                } else {
                    map.put(REMAINING_QUANTITY, 0);
                }

                BigDecimal remainingWeight = new BigDecimal(0);
                BigDecimal remainingVolume = new BigDecimal(0);
                //库位剩余承载重量和体积
                inventorieTemps.forEach(i -> {
                    //获取商品基本单位转换
                    GoodsUnitConversion goodsUnitConversion = goodsUnitConversionService.getOne(new QueryWrapper<GoodsUnitConversion>().lambda()
                            .eq(GoodsUnitConversion::getGoodsCode, i.getGoodsCode())
                            .eq(GoodsUnitConversion::getFlag, 1));
                    remainingWeight.add(goodsUnitConversion.getWeight().multiply(i.getQuantity()));
                    remainingVolume.add(goodsUnitConversion.getVolume().multiply(i.getQuantity()));
                });
                map.put("remainingWeight", remainingWeight);
                map.put(REMAINING_QUANTITY, remainingQuantity);
                locationRemainingBearNum.add(map);
            });
        }


        //获取仓库内所有库位
        List<Location> locationsCache = locationMapper.getAllEffectiveByWarehouseCode(warehouseCode);
        //1、商品混放
        //获取商品混放策略/出库锁/不锁的库位
        List<Location> locationList = locationsCache.stream()
                .filter(r -> StrUtil.equalsAny(r.getIoLock(), "1", "3") &&
                        StrUtil.equals(r.getStorageStrategy(), "0"))
                .collect(Collectors.toList());

        //过滤所有承载数量直接符合收货数量的库位
        List<Location> temp_1 = locationList.stream().filter(r -> {
            Map<String, Object> bearType = locationBearTypeCache.get(r.getBearType());
            double czsl = MapUtil.getDouble(bearType, "num");
            return czsl > (receiveRegister.getReceivedQuantity().doubleValue());
        }).collect(Collectors.toList());
        if (IterUtil.isNotEmpty(temp_1)) {
            //查询
            temp_1.stream().filter(r -> {
                //查询库位上的所有库存
                List<Inventory> inventorieTemps = inventoryService.list(new QueryWrapper<Inventory>().lambda().eq(Inventory::getLocation, r.getCode()).gt(Inventory::getQuantity, 0));
                //库位上存在的商品种数
                int count = inventorieTemps.stream().collect(Collectors.groupingBy(Inventory::getGoodsCode)).size();


                //获取库位最大承载sku数
                Map<String, Object> bearType = locationBearTypeCache.get(r.getBearType());
                int skuQuantity = MapUtil.getInt(bearType, "skuQuantity");


                BigDecimal quantity = inventorieTemps.stream().map(Inventory::getQuantity).reduce(BigDecimal.ZERO, BigDecimal::add);
                return true;
            }).collect(Collectors.toList());


        }


        return null;
    }


    /**
     * 根据库存信息生成批次号
     *
     * @param inventory
     * @return
     */
    private String generatorLot(ReceiveRegister inventory, String shipmentCode, String goodCode) {
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMdd");
        StringBuilder lot = new StringBuilder();

        // 根据货主code，获取货主的编码   XYTODO TODO  货主的partyCache是否可以添加对编码的缓存，减少查询
        String shipmentSerialNumber = "";
        if (StrUtil.isNotBlank(shipmentCode)) {
            Shipment shipment = shipmentService.getOne(new QueryWrapper<Shipment>().lambda().eq(Shipment::getPartyCode, shipmentCode));
            if (shipment != null) {
                shipmentSerialNumber = StrUtil.blankToDefault(shipment.getPartyCode(), "");
            }
        }
        lot.append(shipmentSerialNumber);

        lot.append("|" + goodCode);

        if (inventory.getGmtManufacture() != null) {
            lot.append("|" + dtf.format(inventory.getGmtManufacture()));
        }

        if (inventory.getGmtExpire() != null) {
            lot.append("|" + dtf.format(inventory.getGmtExpire()));
        }

        if (inventory.getGmtStock() != null) {
            lot.append("|" + dtf.format(inventory.getGmtStock()));
        }

        if (inventory.getReceiveLot() != null) {
            lot.append("|" + inventory.getReceiveLot());
        }

        if (StrUtil.isNotBlank(inventory.getSupplier())) {
            lot.append("|" + inventory.getSupplier());
        }

        if (StrUtil.isNotBlank(inventory.getExtendOne())) {
            lot.append("|" + inventory.getExtendOne());
        }

        if (StrUtil.isNotBlank(inventory.getExtendTwo())) {
            lot.append("|" + inventory.getExtendTwo());
        }

        if (inventory.getExtendThree() != null) {
            lot.append("|" + inventory.getExtendThree());
        }
        if (inventory.getExtendFour() != null) {
            lot.append("|" + inventory.getExtendFour());
        }

        if (inventory.getExtendFive() != null) {
            lot.append("|" + dtf.format(inventory.getExtendFive()));
        }
        if (inventory.getExtendSix() != null) {
            lot.append("|" + dtf.format(inventory.getExtendSix()));
        }

        return lot.toString();
    }

    /**
     * 推荐库位简易版
     *
     * @param warehouseCode
     * @param item
     * @return
     */
    @Override
    public Location simpleRecommendedLocation(String warehouseCode, final ReceiveItemWithRegisterVO item) {
        Location location = null;

        //查询该批号库存是否已经有
        List<Inventory> inventories = inventoryService.list(new QueryWrapper<Inventory>().lambda()
                .eq(Inventory::getLot, item.getLot()));

        if (IterUtil.isNotEmpty(inventories)) {
            Inventory inventory = inventories.get(0);
            if(inventory!=null){
                String locationCode = inventory.getLocation();
                location = locationCache.getObj(locationCode);
            }
        }
        if(location != null){
            return location;
        }

        //获取仓库内所有库位
        List<Location> locationsCache = locationMapper.getAllEffectiveByWarehouseCode(warehouseCode);

        //根据混放策略过滤库位
        List<Location> locations = locationsCache.stream()
                .filter(r -> StrUtil.equalsAny(r.getIoLock(), "1", "3"))
                .sorted(Comparator.comparing(Location::getShelvesNumber, Comparator.nullsLast(Comparator.naturalOrder())))
                .collect(Collectors.toList());
        if (IterUtil.isEmpty(locations)) {
            return null;
        }

        Map<String, Object> goods = goodsCache.get(item.getGoodsCode());
        String storageStrategy = MapUtil.getStr(goods, "storageStrategy");
        if (StrUtil.isBlank(storageStrategy)) {
            //TODO 推荐仓库第一个库位
            Location location1 = locations.get(0);
            if(null!=location1){
                return location1;
            }
        } else {
            //根据混放策略过滤库位
            List<Location> locationList = locations.stream()
                    .filter(r -> StrUtil.equals(r.getStorageStrategy(), storageStrategy)).sorted(Comparator.comparing(Location::getShelvesNumber, Comparator.nullsLast(Comparator.naturalOrder())))
                    .collect(Collectors.toList());
            if (IterUtil.isEmpty(locationList)) {
                return null;
            }
            Location location1 = locationList.get(0);
            if(null!=location1){
                return location1;
            }
        }
        return null;
    }
}
